package com.pai4j.pgc.service.community;
import com.pai4j.common.bean.PAIPageResponseBeanUtil;
import com.pai4j.common.constants.MessageConstants;
import com.pai4j.common.enums.RedisKeyEnum;
import com.pai4j.common.enums.community.*;
import com.pai4j.common.listener.event.CommunityContentEvent;
import com.pai4j.common.listener.event.data.CommunityEventData;
import com.pai4j.common.util.HtmlUtil;
import com.pai4j.common.util.JsonUtil;
import com.pai4j.common.util.RedisUtil;
import com.pai4j.common.util.ThreadPoolExecutorUtil;
import com.pai4j.domain.community.vo.request.CCAnswerSaveRequestVO;
import com.pai4j.domain.community.vo.response.cc.CTCAnswerBaseVO;
import com.pai4j.domain.community.vo.response.cc.CTCAnswerListVO;
import com.pai4j.domain.community.vo.response.cc.CTCQuestionBaseVO;
import com.pai4j.domain.vo.dto.CommentReplyMessageDTO;
import com.pai4j.domain.vo.request.request.CommentSaveVO;
import com.pai4j.domain.vo.response.ResourceBaseInfoVO;
import com.pai4j.domain.vo.response.UserBaseResponseInfoVO;
import com.pai4j.pgc.dao.community.ICCAnswerDAO;
import com.pai4j.pgc.entity.community.CCAnswerEntity;
import com.pai4j.pgc.service.behavior.AbstractResourceBehaviorService;
import com.pai4j.pgc.service.behavior.ResourceBehaviorFactory;
import com.pai4j.pgc.service.comment.CommentService;
import com.pai4j.pgc.service.message.builder.CommentReplyMessageBuilder;
import com.pai4j.pgc.service.queue.UserMessageQueue;
import com.pai4j.remote.user.UserServiceClient;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 *  技术社区问答专栏
 *
 * @author: CYM-pai
 * @date: \ 14:11
 **/
@Service
public class CCAnswerService {

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private UserServiceClient userServiceClient;
    @Autowired
    private ICCAnswerDAO answerDAO;
    @Autowired
    private CCQuestionService questionService;
    @Autowired
    private CommentService commentService;
    @Autowired
    private UserMessageQueue messageQueue;
    @Autowired
    private ApplicationContext applicationContext;

    public Long save(CCAnswerSaveRequestVO answerSaveRequest, String userId) {
        CCAnswerEntity answer = this.convertAnswerEntity(answerSaveRequest, userId);
        answer = answerDAO.save(answer);
        // 刷新回复详情缓存
        refreshAnswerBaseCache(answer);
        // 加入问题的最新回复列表缓存
        addQuestionNewAnswerListCache(answer.getQuestionId(), answer.getId(), answer.getCreateDate().getTime());
        questionService.addQuestionAnswerCountCacheAndDB(answerSaveRequest.getQuestionId());
        CCAnswerEntity finalAnswer = answer;
        ThreadPoolExecutorUtil.execute(() ->  {
            this.sendMessage(finalAnswer, userId);
        });

        CTCQuestionBaseVO question = questionService.getQuestionBaseFromCache(answerSaveRequest.getQuestionId());
        CommunityEventData communityEventData = new CommunityEventData();
        communityEventData.setEvent(ESEventEnum.CREATE);
        communityEventData.setType(CommunityDataTypeEnum.QA.getType());
        communityEventData.setPathId(question.getPathId());
        communityEventData.setBizId(question.getId());
        communityEventData.setAuthor(answer.getAuthor());
        communityEventData.setTitle(question.getTitle());
        communityEventData.setDescription(question.getDescription());
        communityEventData.setCover(CollectionUtils.isNotEmpty(question.getImages()) ? question.getImages().get(0) : "");
        communityEventData.setContent(HtmlUtil.getTxtContent(answer.getContent()));
        communityEventData.setSigns(question.getSignStr());
        communityEventData.setLastTime(answer.getCreateDate().getTime());
        CommunityContentEvent event = new CommunityContentEvent(communityEventData);
        applicationContext.publishEvent(event);
        return answer.getId();
    }


    private void sendMessage(CCAnswerEntity answer, String userId) {
        CommentSaveVO commentSaveVO = new CommentSaveVO();
        commentSaveVO.setAuthorId(userId);
        commentSaveVO.setContent(HtmlUtil.getTxtContent(answer.getContent()));
        commentSaveVO.setResourceType(ResourceTypeEnum.CTC_QUESTION.getType());
        commentSaveVO.setResourceId(answer.getQuestionId());
        CTCQuestionBaseVO qa = questionService.getQuestionBaseFromCache(answer.getQuestionId());
        if (qa.getAuthor().equals(userId)) {
            return;
        }
        ResourceBaseInfoVO resourceBaseInfoVO = ResourceBaseInfoVO.of(qa.getId(), qa.getAuthor(), qa.getTitle(), null);
        sendCommentMessage(true, commentSaveVO, resourceBaseInfoVO);
    }

    private void sendCommentMessage(boolean isComment, CommentSaveVO commentSaveVO, ResourceBaseInfoVO resourceBaseInfoVO) {
        String fromUserId = commentSaveVO.getAuthorId();
        UserBaseResponseInfoVO user = userServiceClient.getUserInfoByUserId(commentSaveVO.getAuthorId());
        if (user == null) {
            return;
        }
        String commentContent = commentSaveVO.getContent();
        String content = org.apache.commons.lang3.StringUtils.isBlank(commentContent) ? "[图片]" : commentContent.substring(0, commentContent.length() > 20 ? 20 : commentContent.length());
        MessageConstants.TypeEnum typeEnum = isComment ? MessageConstants.TypeEnum.COMMENT : MessageConstants.TypeEnum.REPLY;
        CommentReplyMessageBuilder commentReplyMessageBuilder = new CommentReplyMessageBuilder();
        CommentReplyMessageDTO commentReplyMessage = commentReplyMessageBuilder.build(typeEnum, fromUserId,
                resourceBaseInfoVO.getAuthor(),
                ResourceTypeEnum.valueOf(commentSaveVO.getResourceType()),
                commentSaveVO.getResourceId().toString(),
                content);
        messageQueue.submit(commentReplyMessage);
    }

    public PAIPageResponseBeanUtil<CTCAnswerListVO> newPageList(Long questionId, String userId, int pageNo, int  pageSize) {
        String key = RedisKeyEnum.CTC_ANSWER_NEW_ZSET_CACHE.getKey(questionId.toString());
        if (!redisUtil.exists(key)) {
            initQuestionNewListCache(questionId);
        }
        List<CTCAnswerListVO> ctcAnswerListVOList = new ArrayList<>();
        long total = redisUtil.zSetSize(key);
        Set<String> answerIdCaches = redisUtil.zreverseRange(key, (pageNo - 1) * pageSize, pageNo * pageSize);
        if (CollectionUtils.isEmpty(answerIdCaches)) {
            return PAIPageResponseBeanUtil.success(pageNo, pageSize, total, ctcAnswerListVOList);
        }
        List<Long> answerIdList = answerIdCaches.stream().map(Long::valueOf).collect(Collectors.toList());
        List<CTCAnswerBaseVO> answerBaseVOList = this.batchGetQuestionAnswerListFromCache(questionId, answerIdList);


        AbstractResourceBehaviorService behaviorService =
                ResourceBehaviorFactory.getBehaviorService(ResourceBehaviorTypeEnum.LIKE.getBehavior());
        Map<Long, Long> likeMap = behaviorService.getBehaviorCountFromCache(ResourceTypeEnum.CTC_ANSWER.getType(), answerIdList);
        Map<Long, Boolean> isLikeMap = new HashMap<>();
        if (org.apache.commons.lang3.StringUtils.isNotBlank(userId)) {
            isLikeMap = behaviorService.getIsBehaviorFromCache(ResourceTypeEnum.CTC_ANSWER.getType(), answerIdList, userId);
        }
        // 批量查询评论量
        Map<Long, Long> commentCountMap = commentService.batchGetCommentCountFromCache(answerIdList, ResourceTypeEnum.CTC_ANSWER.getType());
        List<String> authorIdList = answerBaseVOList.stream().map(CTCAnswerBaseVO::getAuthor).collect(Collectors.toList());
        Map<String, UserBaseResponseInfoVO> answerAuthorMap = userServiceClient.batchGetUserInfoFromCache(authorIdList);
        for (CTCAnswerBaseVO answerBase : answerBaseVOList) {
            CTCAnswerListVO answerList = new CTCAnswerListVO();
            answerList.setAnswer(answerBase);
            answerList.setAnswerAuthor(answerAuthorMap.get(answerBase.getAuthor()));
            answerList.setLikeCount(likeMap.getOrDefault(answerBase.getId(), 0L));
            answerList.setCommentCount(commentCountMap.getOrDefault(answerBase.getId(), 0L));
            answerList.setIsLike(isLikeMap.getOrDefault(answerBase.getId(), false));
            ctcAnswerListVOList.add(answerList);
        }
        return PAIPageResponseBeanUtil.success(pageNo, pageSize, total, ctcAnswerListVOList);
    }

    public List<CTCAnswerBaseVO> batchGetQuestionAnswerListFromCache(Long questionId, List<Long> answerIdList) {
        if (CollectionUtils.isEmpty(answerIdList)) {
            return Collections.emptyList();
        }
        List<CTCAnswerBaseVO> list = new ArrayList<>();
        List<String> answerIdListStr = answerIdList.stream().map(String::valueOf).collect(Collectors.toList());
        String key = RedisKeyEnum.CTC_ANSWER_DETAIL_HASH_CACHE.getKey(questionId.toString());
        List<String> answerCacheList = redisUtil.hmget(key, answerIdListStr);
        for (int i = 0; i < answerIdList.size(); i++) {
            Long answerId = answerIdList.get(i);
            String cache = answerCacheList.get(i);
            CTCAnswerBaseVO answer;
            if (StringUtils.isEmpty(cache)) {
                answer = refreshAnswerBaseCache(answerId);
            } else {
                answer = JsonUtil.fromJson(cache, CTCAnswerBaseVO.class);
            }
            list.add(answer);
        }
        return list;
    }

    /**
     * 加入到问题的最新列表缓存
     * @param qid
     * @param aid
     * @param time
     */
    private void addQuestionNewAnswerListCache(Long qid, Long aid, Long time) {
       String key = RedisKeyEnum.CTC_ANSWER_NEW_ZSET_CACHE.getKey(qid.toString());
       if (!redisUtil.exists(key)) {
           initQuestionNewListCache(qid);
           return;
       }
       redisUtil.zadd(key, aid.toString(), time, RedisKeyEnum.CTC_ANSWER_NEW_ZSET_CACHE.getExpireTime());
    }

    private void initQuestionNewListCache(Long qid) {
        String key = RedisKeyEnum.CTC_ANSWER_NEW_ZSET_CACHE.getKey(qid.toString());
        List<CCAnswerEntity> answerEntities = answerDAO.findAllByQuestionIdAndDeletedOrderByIdDesc(qid, false);
        Set<ZSetOperations.TypedTuple<String>> tuples = answerEntities.stream().map(answer -> {
            String value = String.valueOf(answer.getId());
            Double score = Double.valueOf(answer.getCreateDate().getTime());
            return new DefaultTypedTuple<>(value, score);
        }).collect(Collectors.toSet());
        redisUtil.zadd(key, tuples, RedisKeyEnum.CTC_ANSWER_NEW_ZSET_CACHE.getExpireTime());
    }

    private CTCAnswerBaseVO refreshAnswerBaseCache(Long answerId) {
        CCAnswerEntity answerEntity = answerDAO.findById(answerId).orElse(null);
        return refreshAnswerBaseCache(answerEntity);
    }

    public CCAnswerEntity getAnswerById(Long answerId) {
        return answerDAO.findById(answerId).orElse(null);
    }

    /**
     * 刷新回复详情缓存
     * @param answer
     * @return
     */
    private CTCAnswerBaseVO refreshAnswerBaseCache(CCAnswerEntity answer) {
        if (answer == null) {
            return null;
        }
        String key = RedisKeyEnum.CTC_ANSWER_DETAIL_HASH_CACHE.getKey(answer.getQuestionId().toString());
        CTCAnswerBaseVO answerBase = this.convertCTCAnswerBaseVO(answer);
        redisUtil.hset(key, answer.getId().toString(), JsonUtil.toJsonString(answerBase), RedisKeyEnum.CTC_ANSWER_DETAIL_HASH_CACHE.getExpireTime());
        return answerBase;
    }


    private CTCAnswerBaseVO convertCTCAnswerBaseVO(CCAnswerEntity answer) {
        CTCAnswerBaseVO answerBase = new CTCAnswerBaseVO();
        BeanUtils.copyProperties(answer, answerBase);
        answerBase.setCreateTime(answer.getCreateDate().getTime());
        return answerBase;
    }

    private CCAnswerEntity convertAnswerEntity(CCAnswerSaveRequestVO answerSaveRequest, String userId) {
        Date now = new Date();
        CCAnswerEntity answerEntity;
        Long id = answerSaveRequest.getId();
        if (id != null) {
            answerEntity = answerDAO.findById(id).orElse(null);
            Assert.isTrue(answerEntity != null, "回答更新失败，未找到回复内容");
            Assert.isTrue(answerEntity.getAuthor().equals(userId), "无权限！");
        } else {
            answerEntity = new CCAnswerEntity();
            answerEntity.setQuestionId(answerSaveRequest.getQuestionId());
            answerEntity.setAuthor(userId);
            answerEntity.setCreateBy(userId);
            answerEntity.setCreateDate(now);
            answerEntity.setTop(false);
            answerEntity.setValid(false);
            answerEntity.setDeleted(false);
        }
        answerEntity.setContent(answerSaveRequest.getContent());
        answerEntity.setUpdateBy(userId);
        answerEntity.setUpdateDate(now);
        return answerEntity;
    }
}
